/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package model;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.logging.Level;
import java.util.logging.Logger;

import observer.ChangeNotifier;
import observer.ChangeObserver;
import observer.UpdateObserver;
import view.PlayerCanvas;
import view.admin.AdminFrame;
import view.contents.ImageBox;
import config.Debug;
import config.Helper;
import config.Settings;

/**
 * The player will notify the gui if there is any change by using the observer
 * pattern
 * 
 * @author Yang Wang <yang@digitalmenubox.com>
 */
public class Player implements UpdateObserver, ChangeNotifier {
	// object for singleton design pattern

	public static final boolean PLAY_VIDEO = false;
	// the singleton instance of Player, created when class is loaded
	private static Player instance = new Player();
	// playInventory must equal to true when the player is playing inventory
	// items
	private boolean playingInventory = false;
	// the last time the player has played a record
	private long lastPlayedTime = System.currentTimeMillis();
	private boolean updated = false;
	private List<Playlist> playlists = new ArrayList<Playlist>();
	private List<PlaylistRecord> records = new ArrayList<PlaylistRecord>();
	private List<PlaylistPage> pages = new ArrayList<PlaylistPage>();
	private List<Container> containers = new ArrayList<Container>();
	// observers is used to manage observers notified by this object
	private List<ChangeObserver> observers = new ArrayList<ChangeObserver>();
	private Queue<PlaylistRecord> playQueue;
	private PlaylistRecord currentPlaylistRecord;
	private InventoryManager inventoryManager = InventoryManager.getInstance();

	public boolean isPlayingInventory() {
		return playingInventory;
	}

	public void setPlayingInventory(boolean playingInventory) {
		this.playingInventory = playingInventory;
	}

	public InventoryManager getInventoryManager() {
		return inventoryManager;
	}

	public Queue<PlaylistRecord> getPlayQueue() {
		return playQueue;
	}

	public boolean isUpdated() {
		return updated;
	}

	public void setUpdated(boolean updated) {
		this.updated = updated;
	}

	// plays the next playlist record in the queue
	public boolean play() {
		if (this.playQueue.isEmpty()) {
			refreshData();
			if (this.playQueue.isEmpty()) {
				return false;
			}
		}

		// if the player is has downloaded new data
		// we must regenerate the playlist and go to the record that is
		// immediately after the record that was playing before the update
		// if we cannot find such record, play from the beginning
		if (this.isUpdated()) {
			this.playQueue = generateRecordQueue();
			if (this.currentPlaylistRecord != null) {
				while (!this.playQueue.isEmpty()) {
					if (this.playQueue.poll().getId() == this.currentPlaylistRecord.getId()) {
						break;
					}
				}
				// if we cannot find the last record played, then we have to
				// regenerate the records
				if (this.playQueue.isEmpty()) {
					this.playQueue = generateRecordQueue();
				}
			}
			this.setUpdated(false);
		}

		if (!this.playQueue.isEmpty()) {
			while (!this.playQueue.isEmpty()) {
				PlaylistRecord rec = this.playQueue.poll();
				if (rec.isPlayableNow()) {
					// TODO: REMOVE THIS TO PLAY VIDEO
					if (Player.PLAY_VIDEO == false && rec.getContent().getType() == ContentType.VIDEO) {
						// System.out.println("DON'T PLAY VIDEO");
						// continue;
					}
					this.setCurrentPlaylistRecord(rec);
					this.lastPlayedTime = System.currentTimeMillis();
					return true;
				} else {
					// System.out.println("Not playable");
				}
			}
		}

		return false;
	}

	public PlaylistRecord getNextRecord() {
		Queue<PlaylistRecord> q = this.generateRecordQueue();
		int size = q.size(), count = 0;
		boolean notDone = true;
		boolean foundCurrentRecord = false;
		while(notDone) {
			PlaylistRecord r = q.poll();
			if(r.equals(this.currentPlaylistRecord)) {
				foundCurrentRecord = true;
				continue;
			} else {
				q.offer(r);
			}
			if(foundCurrentRecord && r.isPlayableNow()) {
				return r;
			}
			count++;
			if(count>=size) {
				notDone = false;
			}
		}
		return null;
	}
	public long getLastPlayedTime() {
		return lastPlayedTime;
	}

	public void setLastPlayedTime(long lastPlayedTime) {
		this.lastPlayedTime = lastPlayedTime;
	}

	public Queue<PlaylistRecord> generateRecordQueue() {
		Queue<PlaylistRecord > playQueue = new LinkedList<PlaylistRecord>();
		for (Playlist playlist : this.playlists) {
			for (PlaylistRecord r : playlist) {
				playQueue.offer(r);
			}
		}
		return playQueue;
	}

	public List<ChangeObserver> getObservers() {
		return observers;
	}

	public void setObservers(List<ChangeObserver> observers) {
		this.observers = observers;
	}

	public List<PlaylistPage> getPages() {
		return pages;
	}

	public void setPages(List<PlaylistPage> pages) {
		this.pages = pages;
	}

	public boolean isPaused() {
		return paused;
	}

	public void setPaused(boolean paused) {
		this.paused = paused;
	}

	private boolean paused = false;

	public List<PlaylistRecord> getRecords() {
		return records;
	}

	public synchronized PlaylistRecord getCurrentPlaylistRecord() {
		return currentPlaylistRecord;
	}

	public synchronized void setCurrentPlaylistRecord(PlaylistRecord currentPlaylistRecord) {
		this.currentPlaylistRecord = currentPlaylistRecord;
		this.notifyChangeObservers();
	}

	public static Player getInstance() {
		return instance;
	}

	private Player() {
		this.generatePlaylists();
		this.generatePlaylistPages();
		this.generateContainers();
		this.generatePlaylistRecords();
		this.playQueue = this.generateRecordQueue();
	}

	public void update() {
		this.refreshData();
		Debug.msg("updating the player");
		ImageBox.resetBuffer();
		PlayerCanvas.getInstance().updateGraphics();
		this.setUpdated(true);
	}

	public void refreshData() {
		this.generatePlaylists();
		this.generatePlaylistPages();
		this.generateContainers();
		this.generatePlaylistRecords();
		this.playQueue = this.generateRecordQueue();
		InventoryManager.getInstance().update();
	}

	private void generatePlaylists() {
		this.playlists = null;
		this.playlists = new ArrayList<Playlist>();
		String sql = String.format("SELECT DISTINCT playlist_id FROM `playlist` WHERE `playlist_active`=1;");
		Connection conn = Settings.getLocalDatabaseConnection();
		ResultSet result = null;
		Statement stmt = null;
		try {
			int numResults = Helper.getNumberOfResults(sql);
			if (numResults <= 0) {
				// there are no records
				return;
			}
			stmt = conn.createStatement();
			result = stmt.executeQuery(sql);
			result.beforeFirst();
			while (result.next() == true) {
				int playlistId = result.getInt("playlist_id");
				Playlist p = new Playlist(playlistId);
				this.addPlaylist(p);
			}

		} catch (SQLException ex) {
			Debug.log("Player.generatePlaylists(): " + ex.getMessage(), Debug.EXCEPTION_LEVEL);
		} finally {
			try {
				if (result != null) {
					result.close();
				}
				if (stmt != null) {
					stmt.close();
				}
			} catch (SQLException ex) {
				Debug.log("Player.generatePlaylists(): " + ex.getMessage(), Debug.EXCEPTION_LEVEL);
			}

		}
		Collections.sort(this.playlists);
	}

	private void generateContainers() {
		this.containers = null;
		this.containers = new ArrayList<Container>();
		String sql = String.format("SELECT DISTINCT `container_id` FROM `container`;");
		Connection conn = Settings.getLocalDatabaseConnection();
		ResultSet result = null;
		Statement stmt = null;
		try {
			int numResults = Helper.getNumberOfResults(sql);
			if (numResults <= 0) {
				// there are no records
				return;
			}
			stmt = conn.createStatement();
			result = stmt.executeQuery(sql);
			result.beforeFirst();
			while (result.next() == true) {
				int containerId = result.getInt("container_id");
				Container c = new Container(containerId);
				this.containers.add(c);
			}
		} catch (SQLException ex) {
			Logger.getLogger(Playlist.class.getName()).log(Level.SEVERE, null, ex);
		} finally {
			try {
				if (result != null) {
					result.close();
				}
				if (stmt != null) {
					stmt.close();
				}
			} catch (SQLException ex) {
				Logger.getLogger(Player.class.getName()).log(Level.SEVERE, null, ex);
			}

		}

		// this.recordMap = this.createRecordMapping();
	}
	
	public int getMaximumContainerIndex() {
		int index = 0;
		for(Container c : this.containers) {
				index = Math.max(c.getDepth(), index);
		}
		return index;
	}

	private void generatePlaylistPages() {
		this.pages = null;
		this.pages = new ArrayList<PlaylistPage>();
		String sql = String.format("SELECT DISTINCT page_id FROM `page`;");
		Connection conn = Settings.getLocalDatabaseConnection();
		ResultSet result = null;
		Statement stmt = null;
		try {
			int numResults = Helper.getNumberOfResults(sql);
			if (numResults <= 0) {
				// there are no records
				return;
			}
			stmt = conn.createStatement();
			result = stmt.executeQuery(sql);
			result.beforeFirst();
			while (result.next() == true) {
				int pageId = result.getInt("page_id");
				PlaylistPage p = new PlaylistPage(pageId);
				this.addPage(p);
			}
		} catch (SQLException ex) {
			Logger.getLogger(Playlist.class.getName()).log(Level.SEVERE, null, ex);
		} finally {
			try {
				if (result != null) {
					result.close();
				}
				if (stmt != null) {
					stmt.close();
				}
			} catch (SQLException ex) {
				Logger.getLogger(Player.class.getName()).log(Level.SEVERE, null, ex);
			}

		}
	}

	private void generatePlaylistRecords() {
		this.records = null;
		this.records = new ArrayList<PlaylistRecord>();
		String sql = String.format("SELECT DISTINCT playlist_record_id FROM `playlist_record` order by playlist_id, record_order;");
		Connection conn = Settings.getLocalDatabaseConnection();
		ResultSet result = null;
		Statement stmt = null;
		try {
			int numResults = Helper.getNumberOfResults(sql);
			if (numResults <= 0) {
				// there are no records
				return;
			}
			stmt = conn.createStatement();
			result = stmt.executeQuery(sql);
			result.beforeFirst();
			while (result.next() == true) {
				int recordId = result.getInt("playlist_record_id");
				PlaylistRecord r = new PlaylistRecord(recordId);
				this.records.add(r);
			}
		} catch (SQLException ex) {
			Logger.getLogger(Playlist.class.getName()).log(Level.SEVERE, null, ex);
		} finally {
			try {
				if (result != null) {
					result.close();
				}
				if (stmt != null) {
					stmt.close();
				}
			} catch (SQLException ex) {
				Logger.getLogger(Player.class.getName()).log(Level.SEVERE, null, ex);
			}

		}
	}

	public List<Container> getContainers() {
		return this.containers;
	}

	public List<Playlist> getPlaylists() {
		return playlists;
	}

	public List<PlaylistPage> getPlaylistPages() {
		return pages;
	}

	public void addPage(PlaylistPage p) {
		this.pages.add(p);
	}

	public void addPlaylist(Playlist p) {
		this.playlists.add(p);
	}

	@Override
	public String toString() {
		StringBuilder result = new StringBuilder();
		for (Playlist p : this.playlists) {
			result.append(p.toString() + "\n");
		}
		return result.toString();
	}

	public static void main(String[] args) {
		Player player = new Player();
		for (Container c : player.getContainers()) {
			System.out.println(c);
		}

		for (Playlist plist : player.getPlaylists()) {
			// System.out.println("playlist " + plist.getName());
			for (PlaylistRecord rec : plist) {
				// System.out.println(rec.getRecordOrder());
			}
		}
		return;
		// System.out.println(player.playlists.get(0).getPages());
	}

	/*
     *
     */
	public Container getContainerById(int c) {
		for (Container container : this.containers) {
			if (c == container.getId()) {
				return container;
			}
		}
		return null;
	}

	@Override
	public void registerChangeNotifier(ChangeObserver o) {
		if (!observers.contains(o)) {
			observers.add(o);
		}
	}

	@Override
	public void removeChangeNotifier(ChangeObserver o) {
		if (observers.contains(o)) {
			observers.remove(o);
		}
	}

	@Override
	public void notifyChangeObservers() {
		for (ChangeObserver o : this.observers) {
			o.update();
		}
	}

	@Override
	public void updateChanges() {
		update();
		AdminFrame.getInstance().updateChanges();
	}
}
